# -*- coding: utf-8 -*-
"""
工作助手模块(WorkAssistant)
主要功能：
1. 处理浏览界面与编辑界面的切换逻辑

@Time    : 2023/1/10 8:29
@Author  : 徐良
@File    : workAssistant.py
"""

from typing import Literal, List, cast, Dict, Any
from PySide6.QtCore import Qt, QModelIndex, QDate, QDateTime, QTime
from PySide6.QtGui import QIcon, QPixmap
from PySide6.QtSql import QSqlQueryModel
import share.globalvar as gol  # 全局变量
from PySide6.QtWidgets import QDataWidgetMapper, QToolBar, QMenuBar, QMenu, \
    QSpacerItem, QSizePolicy
from PySide6.QtGui import QAction

from baseClass.tools.dialogUtils import DialogUtils, StandardButton
from utils.billFactory import BillFactory
import logging


class WorkAssistant(BillFactory):
    """工作助手主类，负责界面工具栏和菜单栏的动态管理"""

    def __init__(self, parent=None):
        """初始化工作助手"""
        super().__init__(parent)
        self._dlg_panel = None
        self.body_model_delegate = None

        self.toolbarFlag = None  # 当前工具栏状态标识

        self._control_save_button_switch = 1        # 控制单据表体数据变动触发【保存】的开关，1:默认为表体model, 0: 代理
        self.error_set = set()

        self.dict_name_list = []        # 字典名列表
        self.f7_help_dict_cache ={}     # 帮助字典缓存
        self.field_help_map_dict = {}  # 字段名和帮助字典名字典

        # 设置切换编辑状态时【保存】按钮可见, 默认是可见
        self._save_button_visible = 1
        self.new_bill_pixmap = QPixmap(":/stamp/resource/stamp/新单据.png").scaled(50, 50)


    # region 显示单据编辑状态管理
    def _check_bill_checker(self) -> bool:
        """
        检查单据是否已审核，如果已审核返回True,未审核则返回False
        :return: bool
        """
        checker_flag = True
        if self.m_class == '11' or self.m_class == '10':
            val = self.head_model.record(0).value('FChecker')
            if val == '' or val is None:
                checker_flag = False

        elif self.m_class == '01':
            val = self.body_model.record(0).value('FChecker')
            if val == '' or val is None:
                checker_flag = False

        return checker_flag

    def _check_save_btn_is_enabled(self, info: str='数据已更改，确认退出吗') -> bool:
        """
        检查【保存】是否可用
        :return:
        """
        save_button = self.get_button('保存')
        if save_button is None or not save_button.isEnabled():
            return True
        else:
            reply = DialogUtils.question(info)
            if reply == StandardButton.Cancel:
                return False
        return True


    def _set_save_action_enabled(self, enabled: bool=False) -> None:
        """
        设置工具栏[保存]命令禁用，0禁用， 1可用
        :param enabled:
        :return:
        """
        obj = self.get_button('保存')
        if obj is not None:
            obj.setEnabled(enabled)

    def _set_save_button_visible(self, visible :bool=True) -> None:
        """
        设置工具栏命令按钮可见
        :param visible:
        :return:
        """
        obj = self.get_button('保存')
        if obj is not None:
            obj.setVisible(visible)

    def _set_toolbar_action_visible(self, action_list: List[str], visible:bool) -> None:
        """
        设置工具栏Action不可见
        :param action_list:
        :param visible:
        :return:
        """
        for action_str in action_list:
            obj = self.get_button(action_str)
            if obj is not None:
                obj.setVisible(visible)


    def _set_toolbar_actions_enabled(self) -> None:
        """
        设置工具栏 [保存] [增加一条] [插入一条] [删除一条]的enabled
        :return:
        """
        default_button_list = ['增加一行', '插入一行', '删除一行']
        button_list = []
        for b_name in default_button_list:
            obj = self.get_button(b_name)
            if obj is not None:
                button_list.append(obj)

        check_flag = self._check_bill_checker()
        for obj in button_list:
            obj.setEnabled(not check_flag)

    def _set_audit_action_turn(self) -> None:
        """
        根据单据审核状态设置工具栏按钮的可用状态

        业务规则：
        - 若单据已审核：禁用【审核】按钮，启用【反审核】按钮
        - 若单据未审核：启用【审核】按钮，禁用【反审核】按钮

        确保这两个互斥操作在同一时间只有一个可用

        :return: 无返回值
        """
        # 检查当前单据是否已审核
        is_audited = self._check_bill_checker()

        # 获取工具栏按钮引用（提前获取避免重复调用）
        audit_button = self.get_button('审核')  # 审核按钮对象
        reverse_button = self.get_button('反审核')  # 反审核按钮对象

        # 根据审核状态设置按钮可用状态
        if is_audited:
            # 已审核状态下的按钮配置
            if audit_button is not None:
                audit_button.setEnabled(False)  # 禁用审核按钮
            if reverse_button is not None:
                reverse_button.setEnabled(True)  # 启用反审核按钮
        else:
            # 未审核状态下的按钮配置
            if audit_button is not None:
                audit_button.setEnabled(True)  # 启用审核按钮
            if reverse_button is not None:
                reverse_button.setEnabled(False)  # 禁用反审核按钮

    def _set_bill_enabled(self) -> None:
        """
        根据单据审核状态，设置表头，表体是否可编辑
        :return:
        """
        check_flag = self._check_bill_checker()
        for obj in self.h_obj_list:
            if obj is not None:
                obj.setEnabled(not check_flag)

        # 获取代理
        default_delegate = self.billTableViewEdit.itemDelegate()
        if check_flag:
            default_delegate.all_columns_editable = False
        else:
            default_delegate.all_columns_editable = True

    # def _set_bill_read_only(self) -> None:
    #     """
    #     设置单据界面只读
    #     :return:
    #     """
    #     default_delegate = self.billTableViewEdit.itemDelegate()
    #     default_delegate.all_columns_editable = False
    #     for obj in self.h_obj_list:
    #         if obj is not None:
    #             if isinstance(obj, Check):
    #                 obj.setEnabled(False)
    #             elif isinstance(obj, Comb):
    #                 obj.setEnabled(False)
    #             else:
    #                 obj.setReadOnly(True)


    def _set_edit_action_toolbar_use(self, use_flag: bool) -> None:
        """
        设置增加一行 插入一行 删除一行 审核 反审核 保存工具栏命令是否可用用
        :return:
        """
        toolbar_button_list = ['增加一行', '插入一行', '删除一行', '审核', '反审核', '保存']
        button_list = []
        for b_name in toolbar_button_list:
            obj = self.get_button(b_name)
            if obj is not None:
                button_list.append(obj)

        for button in button_list:
            button.setEnabled(use_flag)

    def _read_bill_edit_after(self) -> None:
        """
        调度方法
        完成单据读入编辑页面后的动作
        """
        self._set_label_title()
        self.auditFunction.check_bill_audit_status()
        self._set_toolbar_actions_enabled()
        self._set_bill_enabled()
        self._set_save_action_enabled()
        self._set_audit_action_turn()

    def _page_go_back(self):
        """
        返回浏览页面  重新设置工具栏上的命令
        :return:
        """
        self.switch_to_page('pageBrowse')           # 调用baseQWidget中的方法
        # 清除工具栏上的action   此状态为browser
        self._clean_toolbar_menubar()
        # 重设工具
        self._reset_toolbar_menubar('browse')  # 设置到browse类型的面板

        self.m_save_type = 1  # 置维护记录标志

        self._save_button_visible = 1
        self.goBackSignal.emit('ok')          # 发出‘退回’完成信号

    def _set_label_title(self) -> None:
        """
        设置表头抬头和图片
        :return:
        """
        # 设置抬头

        self.lbl_billEditName.setText(self.page_bill_label_title)

    # def _set_mapper(self) -> None:
    #     """
    #     设置数据映射关系
    #
    #     Args:
    #         mapper: 数据映射器
    #     """
    #     # 清除映射关系
    #
    #     self.mapper.clearMapping()
    #     self.mapper.setModel(self.head_model)
    #     self.mapper.setSubmitPolicy(QDataWidgetMapper.SubmitPolicy.AutoSubmit)
    #
    #     # 设置代理（支持时间、日期、日期时间）
    #     self.mapper.setItemDelegate(DateTimeStringDelegate())
    #
    #     if len(self.h_visible_list) != len( self.h_obj_list):
    #         logging.info('sql字段和配置字段长度不一致')
    #
    #     # 建立映射关系
    #     for index, (visible, obj) in enumerate(zip(self.h_visible_list, self.h_obj_list)):
    #
    #         if not visible or obj is None:
    #             continue
    #
    #         if isinstance(obj, CheckBox):
    #             self.mapper.addMapping(obj, index, b"checked")
    #             obj.dataChange.connect(lambda: self.mapper.submit())
    #         elif isinstance(obj, (TimeEdit, DateEdit, DateTimeEdit, F7NumberEdit, F7NameEdit)):
    #             self.mapper.addMapping(obj, index)
    #             # 连接信号强制提交（如果是自定义控件）
    #             if hasattr(obj, "timeChangedSignal"):
    #                 obj.timeChangedSignal.connect(lambda: self.mapper.submit())
    #             elif hasattr(obj, "dateChangedSignal"):
    #                 obj.dateChangedSignal.connect(lambda: self.mapper.submit())
    #
    #
    #         elif isinstance(obj, Combox):
    #             self.mapper.addMapping(obj, index)
    #             obj.currentIndexChanged.connect(lambda: self.mapper.submit())
    #
    #         else:
    #             self.mapper.addMapping(obj, index)
    #
    #     self.mapper.toFirst()


    # endregion


    # region 界面状态管理
    #     模块结构：
    #     - 浏览 / 编辑界面切换处理
    #     - 工具栏清理  菜单栏清理
    #     - 工具栏Action重建
    #     - 菜单Action重建
    #     - 更换body_model的代理

    @staticmethod
    def _clean_toolbar_menubar(gol_dict=None) -> None:
        """
        清除工具栏和菜单栏内容
        :param gol_dict: 全局字典，需包含 'ToolBar' 和 'MenuBar' 键
        :gol 是全程变量，必须通过getValue和setValue获取
        """

        target_dict = gol_dict if gol_dict is not None else gol

        toolbar = target_dict.get_value('ToolBar')
        toolbar.clear()
        menubar = target_dict.get_value('MenuBar')
        menubar.clear()


    # def _reset_toolbar_menubar(
    #         self,
    #         pageStyle: Literal["browse", "edit"]  # 使用字面量类型限定参数取值
    # ) -> None:
    #     """
    #     根据界面模式动态重建工具栏和菜单栏结构
    #     Args:
    #         pageStyle: 界面模式标识符
    #             - 'browse': 浏览模式（只读状态）
    #             - 'edit': 编辑模式（可编辑状态）
    #
    #     Raises:
    #         ValueError: 当传入无效的pageStyle值时
    #         KeyError: 全局变量中缺少必需的ToolBar/MenuBar键时
    #         TypeError: 当数据工厂返回的对象类型不符合预期时
    #
    #     Process Flow:
    #         1. 验证参数有效性
    #         2. 更新实例状态标志
    #         3. 获取全局界面组件
    #         4. 从数据工厂获取最新配置
    #         5. 重建工具栏结构
    #         6. 重建菜单结构
    #     """
    #     # 参数验证层
    #     if pageStyle not in ("browse", "edit"):
    #         raise ValueError(
    #             f"Invalid pageStyle '{pageStyle}'. "
    #             "Allowed values: 'browse', 'edit'"
    #         )
    #
    #     # 状态管理, 表示编辑状态或浏览状态
    #     self.toolbarFlag: str = pageStyle
    #
    #     # 组件获取（带类型断言）
    #     toolbar: QToolBar = gol.get_value("ToolBar")
    #     menubar: QMenuBar = gol.get_value("MenuBar")
    #
    #     # 配置获取
    #     action_model: QSqlQueryModel = DataFactory.get_action_list(str(self.m_number))
    #     menu_model: QSqlQueryModel = DataFactory.get_menu_list(str(self.m_number))
    #
    #     # 界面重建
    #     self._create_toolbar_action(action_model, toolbar, pageStyle)
    #     self._create_menu_action(menu_model, menubar, pageStyle)

    def _create_toolbar_action(
            self,
            model: QSqlQueryModel,
            toolbar: QToolBar,
            pageStyle: Literal["browse", "edit"]  # 限定两种模式
    ) -> None:
        """
        动态创建工具栏Action项并添加到指定工具栏

        Args:
            model: 包含Action配置数据的SQL模型，必须包含以下字段：
                - fname: Action名称（str）
                - fqicon: 图标路径（str）
                - fobjectname: 对象名称（str）
                - ftooltip: 悬停提示（str）
                - fenable: 启用状态（bool/int）
            toolbar: 目标工具栏对象
            pageStyle: 当前界面模式，决定排除哪些Action:
                - 'browse': 浏览模式，排除编辑类Action
                - 'edit': 编辑模式，排除浏览类Action

        Raises:
            AttributeError: 如果模型缺少必需字段
            TypeError: 如果字段类型不符合预期

        Process Flow:
            1. 确定当前模式需要排除的Action列表
            2. 遍历模型记录
            3. 处理分隔符特殊项
            4. 创建标准Action并设置属性
            5. 缓存Action引用到全局变量
        """
        # 确定排除项（类型注解确保self中有这些属性）
        exclude_actions: List[str] = (
            self.browse_action_list if pageStyle == "edit"
            else self.edit_action_list
        )

        action_list: List[QAction] = []

        for i in range(model.rowCount()):
            record = model.record(i)
            action_name: str = record.value("fname")  # type: ignore[union-attr]

            if action_name not in exclude_actions:
                if action_name == "addSeparator":
                    toolbar.addSeparator()
                else:
                    # 创建Action（显式类型转换确保字段存在）
                    action = QAction(
                        QIcon(cast(str, record.value("fqicon"))),
                        cast(str, record.value("fname")),
                        self
                    )
                    action.setObjectName(cast(str, record.value("fobjectname")))
                    action.setToolTip(cast(str, record.value("ftooltip")))
                    action.setEnabled(bool(record.value("fenable")))

                    toolbar.addAction(action)
                    action_list.append(action)

        # 全局缓存（带类型注解）
        gol.set_value("actionList", action_list)

    def _create_menu_action(
            self,
            model: QSqlQueryModel,
            menubar: QMenuBar,
            pageStyle: str  # 保留参数，未来可能使用
    ) -> None:
        """
        递归构建多级菜单结构

        Args:
            model: 包含菜单数据的SQL模型，需包含以下字段：
            menubar: 目标菜单栏对象
            pageStyle: 保留参数，用于未来扩展

        Raises:
            ValueError: 如果模型字段缺失或类型无效
            AttributeError: 如果全局actionTriggered未注册
        """

        def set_child_menu(
                child_model: QSqlQueryModel,
                parent_id: int,
                parent_menu: QMenu
        ) -> None:
            """
            递归构建子菜单结构

            Args:
                child_model: 菜单数据模型
                parent_id: 当前处理的父节点ID
                parent_menu: 父菜单容器对象
            """
            for i in range(child_model.rowCount()):
                record = child_model.record(i)
                try:
                    current_parent = cast(int, record.value("fparentid"))
                    if current_parent == parent_id:
                        is_detail = cast(int, record.value("fdetail")) == 1
                        item_name = cast(str, record.value("fname"))

                        if not is_detail:  # 菜单项
                            sub_menu = parent_menu.addMenu(item_name)
                            set_child_menu(
                                child_model,
                                cast(int, record.value("fid")),
                                sub_menu
                            )
                        else:  # 动作项
                            action = QAction(item_name, self)
                            parent_menu.addAction(action)
                            if (handler := gol.get_value("actionTriggered")):
                                action.triggered.connect(handler)
                except (TypeError, AttributeError) as e:
                    raise ValueError(
                        f"Invalid menu data at row {i}: {str(e)}"
                    ) from e

        # 构建顶级菜单 (flevel=3的项)
        for i in range(model.rowCount()):
            record = model.record(i)
            if (cast(int, record.value("fdetail")) == 0
                    and cast(int, record.value("flevel")) == 3):
                top_menu = menubar.addMenu(cast(str, record.value("fname")))
                set_child_menu(
                    model,
                    cast(int, record.value("fid")),
                    top_menu
                )

       
    # endregion


    # region 统一设置信号响应
    # def _date_edit_binding_mapper(self) -> None:
    #     """
    #     绑定 QDateEdit/QDateTimeEdit 的信号到 mapper.submit，
    #     确保信号只连接一次且不会被覆盖。
    #     """
    #     if not hasattr(self, '_signals_connected'):  # 防止重复连接
    #         self._signals_connected = True
    #         for obj in self.h_obj_list:
    #             if isinstance(obj, (TimeEdit, DateEdit, DateTimeEdit)):
    #                 # 连接 dateChangedSignal
    #                 obj.dateChangedSignal.connect(self._safe_submit)

    def _safe_submit(self) -> bool:
        """安全的提交方法，处理可能的异常"""
        try:
            return self.mapper.submit()
        except Exception as e:
            print(f"Submit failed: {e}")
            return False


    # 设置表头数据变化响应函数 设置表体数据变化响应函数
    def _on_head_mode_data_change(self, index: QModelIndex) -> None:
        """
        响应表头数据变化
        :param index:
        :return:
        """
        self._set_save_action_enabled(True)
        self.head_model.dirty = True

    def _on_body_model_data_change(self, index: QModelIndex) -> None:
        """
        响应表体数据变化
        :return:
        """
        if self._control_save_button_switch:
            self._set_save_action_enabled(True)
        self.body_model.dirty = True
        # 将变动记录保存在model的list_update
        if index.isValid():
            row = index.row()
            if self.body_model.record(row).value('FId'):
                self.body_model.list_update.add(self.body_model.record(row).value('FId'))

    def _set_head_body_data_on(self, link: int=1) -> None:
        """
        设置表体表体数据变化信号响应
        :return:
        """
        if link:
            self.head_model.mesDataChanged.connect(self._on_head_mode_data_change)
            if self.body_model is not None and hasattr(self, 'body_model'):
                self.body_model.dataChangedSignal.connect(self._on_body_model_data_change)
        else:
            self.head_model.mesDataChanged.disconnect(self._on_head_mode_data_change)
            if self.body_model is not None and hasattr(self, 'body_model'):
                self.body_model.dataChangedSignal.disconnect(self._on_body_model_data_change)
    # endregion


    # region 中转、调度，如审核-反审核操作，打印-预览操作，保存、删除操作, go_back,
    def audit_dispatch(self, p_dict: Dict) -> None:
        """
        审核、反审核调度
        :param p_dict:
        :return:
        """
        page_type = p_dict.get('page', 0)
        if page_type == 'pageBrowse':
            self.auditFunction.browse_page_audit(p_dict)
        else:
            self.auditFunction.edit_page_audit(p_dict)

    def print_dispatch(self, p_dict: Dict[str, Any]) -> bool:
        """
        打印、预览调度
        :param p_dict:
        :return:
        """
        print_type = p_dict.get('print_type')
        if print_type:
            # 打印
            self.printFunction.do_print(p_dict)
        else:
            # 预览
            self.printFunction.do_preview(p_dict)

    def transfer_save(self, p_dict: Dict=None) -> bool:
        """
        单据保存中转
        :param p_dict:
        :return:
        """
        return self._save_bill()

    def transfer_go_back(self) -> None:
        """单据编辑界面返回单据浏览界面"""
        # 检查保存命令是否可用
        if not self._check_save_btn_is_enabled('单据数据已变化，确认退回吗'):
            return
        self._page_go_back()

    @staticmethod
    def transfer_red_blue_turn(rob: int=1) -> None:
        """
        单据红蓝字转换，默认蓝字转红字，1为红字
        :param rob:
        :return:
        """
        print('单据红蓝字转换，默认蓝字转红字，1为红字')

    def handle_bill_closed(self) -> None:
        """
        处理单据关闭状态，设置只读状态
        :return:
        """
        closed_val = self.head_model.record(0).value('FClosed')
        if closed_val:
            self._set_bill_read_only()

    # endregion


    # region 增加记录 插入记录 删除记录
    def _handle_xh(self) -> None:
        """
        处理表头的序号
        :return:
        """
        rows = self.body_model.rowCount()
        col = self.get_b_field_column('FXh')
        if col >= 0:
            for i in range(0, rows):
                index = self.body_model.index(i, col)
                self.body_model.setData(index, i + 1)

    def _set_body_model_entity_id(self, row):
        """
        设置表体的父ID,是表头表体类型的要设置父id
        :param row:
        """
        if self.m_class == '11':
            pid_name = self.m_body_pid_name.lower().strip()
            if pid_name in ['', None]:
                pid_name = 'fid'
            column = self.get_b_field_column(pid_name)
            idx = self.body_model.index(row, column)
            pid = self.head_model.record(0).value('FID')
            self.body_model.setData(idx, pid, Qt.EditRole)

    def handle_body_append(self) -> None:
        """
        表体增加一行
        :return:
        """
        row = self.body_model.rowCount()
        self.body_model.insertRows(row, 1)
        self._set_body_model_entity_id(row)
        self._handle_xh()
        self.body_model.dirty = True

    def handle_body_insert(self) -> None:
        """
        表体插入一行
        :return:
        """
        index = self.billTableViewEdit.currentIndex()
        if index.isValid():
            row = index.row()
            self.body_model.insertRows(row, 1)
            self._set_body_model_entity_id(row)
            self._handle_xh()
            self.body_model.dirty = True
        else:
            DialogUtils.info('请选择行', '提示')

    def handle_body_del(self) -> None:
        """
        表体删除一行，如果记录有 `fid` 则加入删除列表，否则直接删除。
        删除后标记数据为脏数据，并重新计算序号（`_handle_xh`）。
        :return: None
        """
        current_index = self.billTableViewEdit.currentIndex()

        # 检查是否选中有效行
        if not current_index.isValid():
            DialogUtils.info("请选择要删除的行", '提示')
            return

        row = current_index.row()
        record = self.body_model.record(row)
        fid = record.value("fid")

        # 确认删除
        if DialogUtils.question(f"确认删除第 {row + 1} 行记录？") == StandardButton.Cancel:
            return

        try:
            if not fid:  # 空字符串或 None 视为新记录，直接删除
                self.body_model.removeRows(row, 1)
            else:  # 已有 fid，加入删除列表
                self.body_model.list_del.add(fid)
                self.body_model.removeRows(row, 1)

            # 标记数据已修改，并重新计算序号
            self.body_model.dirty = True
            self._handle_xh()

        except Exception as e:
            DialogUtils.info(f"删除失败: {str(e)}", '提示')
            logging.error(f"删除记录失败: {e}", exc_info=True)

    # endregion


    # region 保存前工作， 保存后工作 审核前
    def before_save_do(self) -> bool:
        """
        保存前工作
        :return:
        """
        return True

    def after_save_do(self) -> bool:
        """
        保存后工作
        :return:
        """
        pass

    @staticmethod
    def before_audit(audit_type: int) -> bool:
        """
        审核前工作
        :return:
        """
        print('审核前工作')
        return True

    def after_audit(self, audit_type: int) -> None:
        """
        审核后工作
        :param audit_type:
        :return:
        """
        pass


    # endregion


    # region 新建单据，复制单据 保存单据
    def _set_widget_default(self, w_obj: Any, field_name: str) -> None:
        """

        :param w_obj:
        :param field_name:
        :return:
        """
        index = self.get_h_field_column(field_name)
        default = self.h_default_val_list[index]
        w_obj.setText(default)

    # def _widget_clear_and_set_default(self) -> None:
    #     """
    #     清理控件并设置默认值
    #     :return:
    #     """
    #
    #     for obj in self.h_obj_list:
    #
    #         if isinstance(obj, DateEdit):
    #             obj.setDate(QDate.currentDate())
    #
    #         if isinstance(obj, DateTimeEdit):
    #             obj.setDateTime(QDateTime.currentDateTime())
    #
    #         if isinstance(obj, TimeEdit):
    #             obj.setTime(QTime(9, 0))
    #
    #         if isinstance(obj, LineEditStr):
    #             w_name = obj.objectName()
    #             self._set_widget_default(obj, w_name)
    #
    #         if isinstance(obj, LineEditInt):
    #             w_name = obj.objectName()
    #             self._set_widget_default(obj, w_name)
    #
    #         if isinstance(obj, LineEditDecimal):
    #             w_name = obj.objectName()
    #             self._set_widget_default(obj, w_name)

    def _set_head_body_model_clean(self) -> None:
        """
        设置表头表体model干净
        :return:
        """
        self.head_model.dirty = False
        self.head_model.list_update.clear()
        self.head_model.list_del.clear()

        self.body_model.dirty = False
        self.body_model.list_update.clear()
        self.body_model.list_del.clear()

    # def _set_new_bill_env(self) -> None:
    #     """
    #     设置新单据环境
    #     :return:
    #     """
    #     self.m_save_type = -1  # 置维护新记录标志
    #     self.create_models(hid=-1)  # 创建表头、表体model
    #     self.create_form(self.gridLayoutTop, self.gridLayoutFoot)
    #     self._set_mapper()
    #     self._date_edit_binding_mapper()        # 日期时间控件的数据变化绑定mapper
    #
    #     self.billTableViewEdit.setModel(self.body_model)
    #     p_dict = {'work': self}
    #     delegateBill = BodyDelegate(**p_dict)
    #     self.billTableViewEdit.setItemDelegate(delegateBill)
    #     # 设置隐藏列和列宽
    #     self.billTableViewEdit.set_column_width_by_number(self.b_column_width_list)
    #     self.billTableViewEdit.set_visible_column_by_number(self.b_visible_list)
    #
    #     # 有可能存在弹簧，移除弹簧
    #     self._handle_remove_spacer()
    #
    #     # 清除工具栏上的action和菜单项
    #     self._clean_toolbar_menubar()
    #     # # 重设工具栏和菜单项
    #     self._reset_toolbar_menubar('edit')  # 设置到edit类型的面板
    #     self._set_label_title()
    #     self.head_model.dirty = True
    #     if hasattr(self, 'lbl_newFlag'):
    #         self.lbl_newFlag.setPixmap(self.new_bill_pixmap)
    #     apply_styles(self)

    def _after_set_new_bill_env(self) -> None:
        """
        设置完表头FId,FBillNo后的动作
        :return:
        """
        self.switch_to_page('pageBillEdit')  # 调用baseQWidget的方法，转换页面
        self.error_set.clear()                  # 清除错误信息
        self._set_head_body_data_on()           # 设置表头、表体数据变化响应

    # def new_bill(self, fid: int, bill_no: str) -> None:
    #     """
    #     新建一张单据
    #     :param fid: 内码
    #     :param bill_no: 单据编号
    #     :return:
    #     """
    #
    #     # 重新初始化表头、表头model
    #     self._set_new_bill_env()
    #
    #     # 必须放在设置单据内码和单据编码之前，否则如果界面中有日期事件控件，DateTimeStringDelegate代理会让代码清空
    #     self._widget_clear_and_set_default()
    #
    #     col = self.get_h_column_by_field_name(self._id_number_field_name.get('id_name'))
    #     index = self.head_model.index(0, col)
    #     self.head_model.setData(index, fid)
    #
    #     col = self.get_h_column_by_field_name(self._id_number_field_name.get('number_name'))
    #     index = self.head_model.index(0, col)
    #     self.head_model.setData(index, bill_no)
    #     self.h_id = fid  # 保存表单的Fid, 以便保存修改后重读数据
    #     # 重新设置表头干净
    #     self._after_set_new_bill_env()
    #
    #     # 设置保存命令可用
    #     self._set_save_action_enabled(True)
    #     self._set_mapper()
    #
    #     # 切换model，
    #     self.billTableViewEdit.setModel(self.body_model)
    #     # 设置表体初始代理
    #     if self.body_model_delegate is None:
    #         self.body_model_delegate = BodyDelegate(**{'work': self})
    #
    #     self.billTableViewEdit.setItemDelegate(self.body_model_delegate)
    #
    #     # 设置隐藏列和列宽
    #     self.billTableViewEdit.set_column_width_by_number(self.b_column_width_list)
    #     self.billTableViewEdit.set_visible_column_by_number(self.b_visible_list)
    #
    #     # 清除审核、红蓝字，判定结果的图章
    #     self.stampFunction.clear_audit_stamps()


    # def _save_bill(self) -> bool:
    #     # 框架内保存前工作
    #
    #     if not self.before_save_do():
    #         self._set_save_action_enabled(False)
    #         return False
    #
    #     # 检查表头f7控件的隐藏错误的可能
    #     if not self._check_head_f7_correct():
    #         return False
    #
    #     # 检查各模块，各业务单据的逻辑，即self.error_set错误集合里是否有记录
    #     # 在主窗口中
    #     if not self._before_check_logic_business():
    #         # 禁用保存按钮并立即刷新 UI
    #         self._set_save_action_enabled(False)
    #
    #         # 创建对话框并显式设置父窗口
    #         self._dlg_panel = InfoDialogPanel(self)
    #         self._dlg_panel.setText(list(self.error_set))
    #         # 显示对话框并获取返回值
    #         result = self._dlg_panel.exec()
    #         return False
    #
    #     # 检查单据表头必录项
    #     if not self._check_must_be_record_head():
    #         return False
    #
    #     # 表体必录项
    #     must, f7_err = False, False
    #     if self.body_model is not None:
    #         must = self._check_must_be_record_body()
    #         f7_err =  self._check_body_f7_correct()
    #
    #     # 如果只是表头，则无须检查表体的提示项目
    #     if self.m_class != '10':
    #         self._set_hide_tip_column([f7_err, must])
    #         if not must or not f7_err:
    #             self.status_bar_show_message('必录项未输入，或F7被误操作')
    #             return False
    #
    #     save_action = SaveBillAction()
    #     reply = save_action.saveHeadBody(
    #         headTable=self.m_head_table,
    #         headModel=self.head_model,
    #         h_joinList=self.h_Join_sql_list,
    #         h_fieldList=self.h_field_list,
    #         h_typeList=self.h_data_type_list,
    #         bodyTable=self.m_body_table,
    #         bodyModel=self.body_model,
    #         b_joinList=self.b_join_sql_list,
    #         b_fieldList=self.b_field_list,
    #         b_typeList=self.b_data_type_list,
    #         save_type=self.m_save_type
    #     )
    #     if reply:
    #         self.m_save_type = 1       # 置旧单据
    #         self._set_save_action_enabled(False)    # 置保存命令不可用
    #         # self._set_head_body_model_clean()       # 置表头表头model干净
    #         self.error_set.clear()      # 置错误集合干净
    #         fid = self.head_model.record(0).value('FId')
    #         self.read_bill_data_for_edit(fid)
    #         # 去除新单据提示
    #         if hasattr(self, 'lbl_newFlag'):
    #             self.lbl_newFlag.clear()
    #
    #         self.after_save_do()
    #
    #         return True
    #     else:
    #         return False

    def _copy_bill(self, fid: int, bill_no: str) -> None:
        """
        复制单据
        :return:
        """
        # 置表头
        self.m_save_type = -1  # 置旧单据

        col = self.get_h_column_by_field_name(self._id_number_field_name.get('id_name'))
        index = self.head_model.index(0, col)
        self.head_model.setData(index, str(fid))

        col = self.get_h_column_by_field_name(self._id_number_field_name.get('number_name'))
        index = self.head_model.index(0, col)
        self.head_model.setData(index, bill_no)

        self.h_id = fid  # 保存表单的Fid, 以便保存修改后重读数据

        # 置表体FEntityID, 置表体FId=0
        entity_id_col = self.get_b_column_by_field_name('FEntityId')
        id_col =  self.get_b_column_by_field_name('FId')
        self._set_head_body_data_on(0)
        for row in range(0, self.body_model.rowCount()):
            index = self.body_model.index(row, entity_id_col)
            self.body_model.setData(index, self.h_id, Qt.EditRole)

            index = self.body_model.index(row, id_col)
            self.body_model.setData(index, 0, Qt.EditRole)

        self._set_head_body_data_on()

        self._set_head_body_model_clean()
        self.body_model.dirty = True
        self.head_model.dirty = True
        if hasattr(self, 'lbl_newFlag'):
            self.lbl_newFlag.setPixmap(self.new_bill_pixmap)

        self._set_mapper()

    # endregion


    # region 业务逻辑 必录项检查
    def _init_help_dict(self) -> None:
        """
        初始化帮助字典缓存
        :return:
        """
        name_set = set(filter(None, self.b_dict_name_list))
        self.dict_name_list = list(name_set)

        # 建立缓存
        self.f7_help_dict_cache.clear()
        for key in self.dict_name_list:
            self.f7_help_dict_cache[key] = set()  # 帮助字典缓存，使用set()创建空集合

        # 建立字段名与字段名对照字典
        self.field_help_map_dict.clear()
        for field, isf7, help_dict in zip(self.b_field_list, self.b_f7_list, self.b_dict_name_list):
            if help_dict not in ['', None]:
                self.field_help_map_dict[field] = help_dict

    def _read_help_dicts(self) -> None:
        """
        读取单据表体字典字段的代码
        :return:
        """
        for row in range(0, self.body_model.rowCount()):

            for key, value in self.field_help_map_dict.items():

                # 取需要保存到缓存的代码
                number = self.body_model.record(row).value(key)

                # 取保存的位置（集合）
                obj_set = self.f7_help_dict_cache.get(value)
                obj_set.add(number)

    def _before_check_logic_business(self) -> bool:
        """
        检查业务逻辑合规性
        :return:
        """
        err_list = list(self.error_set)
        if len(err_list) > 0:
            return False
        else:
            return True

    # def _check_must_be_record_head(self) -> bool:
    #     """
    #     检查表头必录项
    #     :return:
    #     """
    #     must_flag = True
    #     must_field_list = self._get_h_must_input_field()
    #
    #     for must_field in must_field_list:
    #         label, input_obj = self._get_must_input_lbl_obj(must_field)
    #
    #         if input_obj is None:
    #             continue
    #
    #         if isinstance(input_obj, Combox):
    #             if input_obj.currentText() == '':
    #                 label.setMust(1)
    #                 must_flag = False
    #         elif input_obj.text() == '':
    #             label.setMust(1)
    #             self.status_bar_show_message('请输入表头必录项')
    #             must_flag = False
    #         else:
    #             label.setMust(0)
    #
    #     return must_flag

    def _check_head_f7_correct(self) -> bool:
        """
        检查表f7控件正确性
        :return:
        """
        f7_list = self._get_head_f7_obj()
        ret = True
        for obj in f7_list:
            if obj.correct == 2:
                label = self._get_h_label(obj.objectName())
                if label is not None:
                    label.addError()
                ret = False
        return ret

    # def _get_head_f7_obj(self) -> List[Any]:
    #     """
    #     取表头f7控件，且是可编辑的
    #     :return:
    #     """
    #     obj_list = []
    #     for isedit, obj in zip(self.h_edit_list, self.h_obj_list):
    #         if isedit:
    #             if isinstance(obj, (F7NumberEdit, F7NameEdit)):
    #                 obj_list.append(obj)
    #     return obj_list

    def _check_must_be_record_body(self) -> bool:
        """
        检查表体必录项
        :return:
        """
        def check_must(record) -> bool:
            flag = False
            for must, field, field_name in zip(self.b_must_input_list, self.b_field_list, self.b_field_name_list):
                if must:
                    if record.value(field) in ['', None]:
                        flag = True
                        break
            return flag

        ret = True
        for row in range(0, self.body_model.rowCount()):
            rec = self.body_model.record(row)
            reply = check_must(rec)
            if reply:
                index = self.body_model.index(row, 3)
                self.body_model.setData(index, '🔵')
                ret = False
        return ret

    def _check_body_f7_correct(self) -> bool:
        """
        检查表体项目的f7控件
        :return:
        """

        # 根据实际使用的清空，产生临时的字段名，值列表键值对
        temp_map = {}

        for key, value in self.field_help_map_dict.items():

            number_list = list(self.f7_help_dict_cache.get(value))
            temp_map[key] = number_list

        ret = True
        for row in range(0, self.body_model.rowCount()):

            for key, value in self.field_help_map_dict.items():

                real_number = self.body_model.record(row).value(key)
                if real_number not in temp_map.get(key):
                    index = self.body_model.index(row, 4)
                    self.body_model.setData(index, 2)
                    index = self.body_model.index(row, 2)
                    self.body_model.setData(index, '❌')
                    ret = False

        return ret

    def _set_hide_tip_column(self, hide_column: List[bool]) -> None:
        """

        :param hide_column:
        :param display:
        :return:
        """
        self.billTableViewEdit.setColumnHidden(2, hide_column[0])
        self.billTableViewEdit.setColumnHidden(3, hide_column[1])

    def handel_f7_enter_return(self,field_list: List[str], value_list: List[str]) -> None:
        """
        处理控件传过来的正确的字典值
        :param field_list:
        :param value_list:
        :return:
        """
        for field, val in zip(field_list, value_list):
            h_dict_name = self.field_help_map_dict.get(field.lower(), None)
            if h_dict_name is not None:
                obj_set = self.f7_help_dict_cache.get(h_dict_name)
                obj_set.add(val)

    # endregion


    # 接受F7返回处理
    def _get_h_obj(self, field_name) -> Any:
        """
        根据字段名，获取编辑对象
        :param field_name:
        :return:
        """
        field_name = field_name.lower()
        ret = None
        for obj in self.h_obj_list:
            if obj is not None:
                if obj.objectName() == field_name:
                    ret = obj
        return ret

    def handel_f7_simple_form_value(self, p_dict: Dict[str, List[str]]) -> None:
        """
        接受简表查询返回值，处理表头
        :param p_dict:
        :return:
        """
        field_name_list = p_dict.get('field_name_list')
        field_value_list = p_dict.get('field_value_list')
        for field, value in zip(field_name_list, field_value_list):

            obj = self._get_h_obj(field)
            if obj is not None:
                obj.setText(value)

        self._set_save_action_enabled(True)
        self.head_model.dirty = True
        self.mapper.submit()

    # region 状态栏显示信息
    def status_bar_show_message(self, info: str) -> None:
        """
        状态栏显示信息
        :param info:
        :return:
        """
        statusBar = gol.get_value('statusBar')
        statusBar.clearMessage()
        statusBar.showMessage(info, 6000)

    # endregion